package com.bolt.support.query.nativequery;

import com.bolt.common.BoltConstants;
import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.convert.Convert;
import com.bolt.convention.data.DefaultPageInfo;
import com.bolt.convention.data.PageInfo;
import com.bolt.convention.data.Record;
import com.bolt.convention.exception.DataAccessException;
import com.bolt.support.query.nativequery.parall.ParallelQuery;
import com.bolt.support.query.nativequery.parall.ParallelQueryConfig;
import com.bolt.support.query.nativequery.parall.ParallelQueryExecutor;
import com.bolt.support.query.nativequery.parall.ParallelQueryResult;
import com.bolt.support.query.toolkit.DialectUtil;
import com.bolt.support.query.toolkit.JPAUtil;
import com.bolt.support.spring.jpa.transform.ConfigSqlTransformers;
import com.bolt.support.sqlconfig.base.SqlStatement;
import com.bolt.support.sqlconfig.base.SqlText;
import com.bolt.support.sqlconfig.builder.SqlTemplateFactory;
import org.hibernate.query.internal.NativeQueryImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2021/4/19.
 */
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class NQuery extends AbstractQuery {

    public NQuery(EntityManager entityManager, SqlTemplateFactory sqlTemplateFactory) {
        super(entityManager, sqlTemplateFactory);
    }


    @Override
    public <S> PageInfo<S> findPage(String sqlId, Pageable pageable, Object parameterObject, Class<S> returnClass) {
        SqlStatement sqlStatement = this.getSqlStatement(sqlId);
        SqlText sqlText = sqlStatement.getSqlText(parameterObject);
        Query pageQuery = entityManager.createNativeQuery(sqlText.getSql());
        Query queryCount = entityManager.createNativeQuery(DialectUtil.getCountSql(sqlText.getSql()));
        Properties properties = sqlText.getParameterValues();
        JPAUtil.setParameter(pageQuery, properties);
        JPAUtil.setParameter(queryCount, properties);
        if (pageable.isPaged()) {
            pageQuery.setFirstResult((int) pageable.getOffset());
            pageQuery.setMaxResults(pageable.getPageSize());
        }
        long total = Convert.toLong(queryCount.getSingleResult(), 0l);
        List<S> result = Collections.EMPTY_LIST;
        if (total > 0) {
            List<Object> resultProcessors = sqlStatement.getResultProcessor();
            pageQuery.unwrap(NativeQueryImpl.class)
                    .setResultTransformer(ConfigSqlTransformers.aliasToBean(returnClass, this.sqlTemplateFactory.getConfiguration()));
            result = (List<S>) pageQuery.getResultList();
        }
        return new DefaultPageInfo(pageable.getPageNumber() + 1, pageable.getPageSize(), result, total);
    }

    @Override
    public PageInfo<Record> findPage(String sqlId, Pageable pageable, Object parameterObject) {
        return findPage(sqlId, pageable, parameterObject, Record.class);
    }

    @Override
    public <S> List<S> findList(String sqlId, Object parameterObject, Class<S> returnClass) {
        Query query = doQuery(sqlId, parameterObject, returnClass);
        return query.getResultList();
    }

    @Override
    public List<Record> findList(String sqlId, Object parameterObject) {
        return findList(sqlId, parameterObject, Record.class);
    }

    @Override
    public <S> List<S> findLimit(String sqlId, Object parameterObject, int limit, Class<S> returnClass) {
        Query query = doQuery(sqlId, parameterObject, returnClass);
        query.setFirstResult(0);
        query.setMaxResults(limit);
        return query.getResultList();
    }

    @Override
    public List<Record> findLimit(String sqlId, Object parameterObject, int limit) {
        return findLimit(sqlId, parameterObject, limit, Record.class);
    }


    @Override
    public List<Record> findLimit(String sqlId, Object parameterObject, Pageable pageable) {
        Query query = doQuery(sqlId, parameterObject, Record.class);
        if (pageable.isPaged()) {
            query.setFirstResult((int) pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
        }
        return query.getResultList();
    }

    @Override
    public <S> List<S> findLimit(String sqlId, Object parameterObject, Pageable pageable, Class<S> returnClass) {
        Query query = doQuery(sqlId, parameterObject, returnClass);
        if (pageable.isPaged()) {
            query.setFirstResult((int) pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
        }
        return query.getResultList();
    }

    @Override
    public <S> S findOne(String sqlId, Object parameterObject, Class<S> returnClass) {
        Query query = doQuery(sqlId, parameterObject, returnClass);
        List result = query.getResultList();
        return (S) IteratorUtil.getFirst(result);
    }

    public <S> NativeQueryImpl doQuery(String sqlId, Object parameterObject, Class<S> returnClass) {

        try {
            SqlStatement sqlStatement = this.getSqlStatement(sqlId);
            SqlText sqlText = sqlStatement.getSqlText(parameterObject);
            Query query = entityManager.createNativeQuery(sqlText.getSql());
            Properties properties = sqlText.getParameterValues();
            JPAUtil.setParameter(query, properties);
            List<Object> resultProcessors = sqlStatement.getResultProcessor();
            NativeQueryImpl nativeQuery = query.unwrap(NativeQueryImpl.class);
            nativeQuery.setResultTransformer(ConfigSqlTransformers.aliasToBean(returnClass, this.sqlTemplateFactory.getConfiguration()));

            return nativeQuery;
        } catch (Exception ex) {
            throw new DataAccessException(ex);
        }

    }

    @Override
    public Record findOne(String sqlId, Object parameterObject) {
        return findOne(sqlId, parameterObject, Record.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int executeSql(String sqlId, Object parameterObject) {
        SqlStatement sqlStatement = this.getSqlStatement(sqlId);
        SqlText sqlText = sqlStatement.getSqlText(parameterObject);
        Query query = entityManager.createNativeQuery(sqlText.getSql());
        Properties properties = sqlText.getParameterValues();
        JPAUtil.setParameter(query, properties);
        return query.executeUpdate();
    }

    @Override
    public List<ParallelQueryResult> parallelQuery(List<ParallelQuery> parallelQueries, ParallelQueryConfig parallelConfig) {
        if (IteratorUtil.isEmpty(parallelQueries)) {
            return null;
        }
        ParallelQueryConfig parallConfig = parallelConfig;
        if (parallConfig == null) {
            parallConfig = new ParallelQueryConfig();
        }
        // 并行线程数量(默认最大十个)
        if (parallConfig.getMaxThreads() == null) {
            parallConfig.maxThreads(10);
        }
        int thread = parallConfig.getMaxThreads();
        if (parallelQueries.size() < thread) {
            thread = parallelQueries.size();
        }
        List<ParallelQueryResult> results = new ArrayList<>();
        ExecutorService pool = null;
        try {
            pool = Executors.newFixedThreadPool(thread);
            List<Future<ParallelQueryResult>> futureResult = new ArrayList<>();
            Future<ParallelQueryResult> future;
            for (ParallelQuery parallelQuery : parallelQueries) {
                // NativeQueryImpl nativeQuery = doQuery(parallelQuery.getSqlId(), parallelQuery.getBindings(), parallelQuery.getReturnClass());
                ParallelQueryExecutor executor = new ParallelQueryExecutor(this.entityManager, this.sqlTemplateFactory, parallelQuery);
                future = pool.submit(executor);
                futureResult.add(future);
            }
            pool.shutdown();
            // 设置最大等待时长
            if (parallConfig.getMaxWaitSeconds() != null) {
                pool.awaitTermination(parallConfig.getMaxWaitSeconds(), TimeUnit.SECONDS);
            } else {
                pool.awaitTermination(BoltConstants.PARALLEL_MAXWAIT_SECONDS, TimeUnit.SECONDS);
            }
            ParallelQueryResult item;
            int index = 0;
            for (Future<ParallelQueryResult> result : futureResult) {
                index++;
                item = result.get();
                // 存在执行异常则整体抛出
                if (item != null && !item.isSuccess()) {
                    throw new DataAccessException("第:{} 个sql执行异常:{}!", index, item.getMessage());
                }
                results.add(item);
            }
        } catch (Exception ex) {
            throw new DataAccessException(ex);
        } finally {
            if (pool != null) {
                pool.shutdownNow();
            }
        }
        return results;
    }


}

 